import { default as axios, AxiosRequestConfig, AxiosResponse } from "axios";
import { load, save, persist } from "./localcache";
import store from "@/store";
import { ElLoading } from "element-plus";

/**
 * 请求参数选项对象
 */
interface IAjaxParam extends AxiosRequestConfig {
    /**
     * 重试次数，默认0不重试
     */
    retry?: number,
    /**
     * 是否使用缓存
     */
    useCache?: boolean
}

interface IAjaxResponseData {
    [index: string]: any,
    /**
     * 响应结果，正数成功，负数失败
     */
    code: number,
    /**
     * 响应消息
     */
    msg: string,
    /**
     * 响应数据集
     */
    data?: any,
}

/**
 * 节流请求参数
 */
interface IRestrictParam extends IAjaxParam {
    /**
     * 节流的key
     */
    key?: string,
    /**
     * 节流的时长（毫秒）
     */
    restrictTimeout?: number
}


// const baseURL = "https://www.forerunnercollege.com/so2Api";
// const baseURL = "http://121.196.153.128/ssApi";
// const baseURL = "http://127.0.0.1:8083";
const baseURL = "/api";

// 用于防抖
const set = new Set<string>();

// 用于节流
const restrictMap = new Map<string, number>();

persist(1);


/**
 * 使用axios发起异步请求
 * @param param1 请求参数选项对象
 */
function ajax(param: IAjaxParam): Promise<AxiosResponse<IAjaxResponseData, any>>;

/**
 * 使用axios发起异步请求
 * @param url 请求地址
 * @param data 请求参数 
 * @param method 请求方法
 * @param params url表单参数对象
 * @param retry 重试次数（默认为0，不重试）
 */
function ajax(url: string,
    data?: object,
    method?: 'get' | 'post',
    params?: object,
    retry?: number,
    useCache?: boolean): Promise<AxiosResponse<IAjaxResponseData, any>>;


function ajax(param1: IAjaxParam | string,
    data: object | undefined = undefined,
    method = 'post',
    params: object | undefined = undefined,
    retry = 0,
    useCache = true,
): Promise<AxiosResponse<IAjaxResponseData, any>> {
    let axiosParam: IAjaxParam;

    let url: string | undefined;
    // 如果第一个参数是对象，那么优先采用，覆盖三个参数
    if (typeof param1 == 'object') {
        axiosParam = param1;
        if (axiosParam.data) {
            data = axiosParam.data;
        }
        if (!axiosParam.method) {
            axiosParam.method = "post";
        }
        if (axiosParam.baseURL == null) {
            axiosParam.baseURL = baseURL;
        }
        method = axiosParam.method
        url = axiosParam.url;
        params = axiosParam.params;
        retry = axiosParam.retry || 0;
        if (axiosParam.useCache == null) {
            axiosParam.useCache = true;
        }
        useCache = axiosParam.useCache;
    } else {
        url = param1;
        axiosParam = {
            url,
            data,
            method,
            params,
            baseURL,
            useCache
        }
    }

    const doAjax = (): Promise<any> => {
        let key: string;
        // 如果使用缓存
        if (useCache) {
            // 将请求参数json序列化（我的系统没有考虑get的url参数，自己写的接口偷懒全用的post）
            const jsonStr = data ? JSON.stringify(data) : "";
            // 将url、请求参数、请求方法拼接在一起形成key
            key = url + jsonStr + method;


            // 加载缓存数据，如果有缓存数据直接返回不进行网络请求
            const obj = load(key);
            if (null != obj) {
                set.delete(key);
                return Promise.resolve({ data: obj });
            }
            // console.log("key = ", key);
            // key也用于防抖判断，如果上次同样的请求还没有结束，不允许重复提交
            if (set.has(key)) {
                console.log("请勿重复提交：", key);
                alert({ type: "error", message: "请勿重复提交" });
                return Promise.resolve({ data: { code: -1, msg: "请勿重复提交" } });
            }
            // 当前key添入到set中，以便判断重复请求
            set.add(key);
        }


        // 这里做个简单判断，如果url包括/insert，/update，/delete那么来个遮罩
        let loadingInstance: any = null;
        if (url?.indexOf("/insert") != -1
            || url?.indexOf("/update") != -1
            || url?.indexOf("/delete") != -1
        ) {
            loadingInstance = ElLoading.service();
        }

        // console.log("axiosParam = ", axiosParam);
        const result = axios(axiosParam);
        result.finally(() => {
            if (null != loadingInstance) {
                loadingInstance.close();
            }
            // 如果生成了key表示使用了缓存
            if (key) {
                set.delete(key);
            }
        })
        return result;
    }

    /**
     * 重试
     * @returns 
     */
    const doRetry = async (timeout = 3000, retryCount = 1): Promise<any> => {
        try {
            return await doAjax();
        } catch (e) {
            if (retryCount <= retry) {
                return doRetry(timeout * 1.5,retryCount + 1);
            } else {
                return e;
            }
        }
    }

    if (retry <= 0) {
        return doAjax();
    } else {
        console.log("尝试重试")
        return doRetry(3000,1);
    }
}


axios.interceptors.request.use(config => {
    const token = store.state.admin.token;
    if (token) {
        config.headers.Authorization = token;
    }
    return config;
})

// 响应拦截
axios.interceptors.response.use(
    // 成功之后的响应拦截
    res => {
        // 有问题，注释掉
        // if (loadingInstance) {
        //     loadingInstance.stop();
        // }
        // console.log(":", res);
        // code小于0表示逻辑失败
        // 如果为-10表示没有登录，强制去往登陆页面
        // console.log("Res.config = ",res.config);
        if (res.data.code === -10) {
            // router.push("/");
        } else if (res.data.code < 0) {
            // alert(res.data.msg);
            // 这里window.alert被重写了的
            alert({ type: "error", message: res.data.msg });
            // 成功后缓存数据
        } else if (res.data.code >= 1) {
            if ((res.config as any).useCache) {
                save(res.config.url,
                    res.config.data,
                    res.config.method,
                    res.data);
            }
        }
        return res;
    },
    // 失败之后的响应拦截（404,500,400）
    error => {
        console.log("响应失败：", error);
        alert({ type: "error", message: "发生网络错误" });
        // alert("发生网络错误");
        // 返回一个明确的promise对象，避免组件报错，方便组件编写代码
        return Promise.resolve({ data: { code: -1, msg: "网络错误" } });
    }
)


/**
 * 根据url进行节流
 * @param param 
 * @param timeout 节流时长 
 */
function restrict(param: IRestrictParam): Promise<AxiosResponse<IAjaxResponseData, any>> {
    const restrictTimeout = param.restrictTimeout || 3000;
    const key = param.key || param.url || "";
    const timeoutId = restrictMap.get(key);
    if (null != timeoutId) {
        clearTimeout(timeoutId);
    }
    return new Promise((reslove, reject) => {
        const id = setTimeout(() => {
            ajax(param).then(res => {
                reslove(res)
            }, error => {
                reject(error)
            }).finally(() => {
                restrictMap.delete(key);
            });
        }, restrictTimeout);
        restrictMap.set(key, id);
    })
}

export { ajax as default, restrict };